package com.foodev.maven.gwt;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;

import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.codehaus.plexus.util.FileUtils;

/**
 * GWT compiles modules. Checks dependencies' timestamps to ensure 
 * compilation should be done.
 * 
 * @phase compile
 * @goal  compile
 * @requiresDependencyResolution compile
 *
 * @author Martin Algesten
 */
public class CompileMojo extends AbstractGwtScriptMojo {
  
  
  /**
   * The modules
   * 
   * @required
   * @parameter
   */
  private List<String> modules;
  
  
  /**
   * The 'style' parameter, should be one of <code>OBF[USCATED]</code>,
   * <code>PRETTY</code> or <code>DETAILED</code>.
   * (Defaults to <code>OBF</code>).
   * 
   * @parameter
   */
  private String style = null;

  
  /**
   * The maximum amount of heap space to use, default is '256m' (-Xmx256m).
   *
   * @parameter expression="256m"
   */
  private String maxmem = null;
  

  public void execute() throws MojoExecutionException,
                               MojoFailureException {

    try {

      File file = createScriptFile("compile");
      FileWriter fwriter = new FileWriter(file);
      PrintWriter writer = new PrintWriter(fwriter);

      List<String> rawClasspath = buildClasspath();
      
      List<String> depClasspath = filterClasspath( rawClasspath, true );
      
      // compilation should filter out the 'target/classes'
      List<String> classpath = filterClasspath( buildClasspath(), false );
     
      startScript( writer );

      writeClasspath( writer, classpath );

      writeCommands( writer, modules );
      
      writer.close();
      fwriter.close();

      long latestDependentTimestamp = getLatestDependentTimestamp( depClasspath );

      long latestOutputTimestamp = getLatestOutputTimestamp( modules );
      
      if ( latestDependentTimestamp > latestOutputTimestamp ) {
      
        int exitValue = 
          executeProcess(gwtHome, new String[]{file.getAbsolutePath()});
        
        if ( exitValue != 0 )
          throw new MojoExecutionException( "GWT compilation failed." +
          		" Exit value: "+exitValue );

        cleanUpAfterCompile();
        
      } else {
        
        getLog().info("Nothing to compile - GWT modules are up to date");
        
      }
      
    } catch ( MojoExecutionException e ) {
      
      throw e;

    } catch ( Exception e ) {

      getLog().error(e);

      throw new MojoFailureException( "GWT compilation failed." );
      
    }
      
  }
  

  /**
   * Writes out the GWT compilation lines into the script.
   * @param writer The writer to write to
   * @param modules The modules.
   * @throws IOException
   */
  private void writeCommands( PrintWriter writer, List<String> modules )
    throws IOException {
    
    for( String module : modules ) {
      writeCommand( writer, module );
    }
    
  }

  
  /**
   * Writes out a single GWT compilation line into the script.
   * @param writer The writer to write to.
   * @param module
   * @throws IOException
   */
  private void writeCommand( PrintWriter writer, String module )
    throws IOException {
    
    writer.write( "java" );
    
    if ( maxmem != null ) {
      writer.write( " -Xmx"+maxmem );
    }

    if ( isWindows() ) {
      writer.write( " -cp %CLASSPATH%" );
    }
    
    writer.write( " com.google.gwt.dev.GWTCompiler" );

    if ( getLog().isDebugEnabled() ) {
      writer.write( " -logLevel DEBUG" );
    }

    if ( style != null ) {
      writer.write( " -style "+style );
    }
    
    writer.write( " -gen \""+new File( gwtHome, "gen" ).getAbsolutePath()+"\"" );
    
    writer.write( " -out \""+warSourceDirectory.getAbsolutePath()+"\"" );
    
    writer.println( " "+module );
    
  }
  
  
  /**
   * Finds the latest timestamp in the given dependencies classpath. Note
   * that the dependencies includes the source directory.
   * @param classpath the classpath
   * @return The latest timestamp of the classpath entries, directories are
   * recursively checked.
   */
  private long getLatestDependentTimestamp( List<String> classpath ) {
 
    long latest = 0;
    
    for( String element : classpath ) {
      
      File f = new File( element );

      long itemLatest = findLatest( f, 0 );
      
      if ( itemLatest > latest ) latest = itemLatest;
      
    }
    
    return latest;
    
  }

  
  /**
   * Checks the latest timestamp of the output modules in target.
   * @param modules The GWT module to check.
   * @return
   */
  private long getLatestOutputTimestamp( List<String> modules ) {
    
    long latest = 0;
    
    for( String module : modules ) {
      
      File f = new File( warSourceDirectory, module );

      long itemLatest = findLatest( f, 0 );
      
      if ( itemLatest > latest ) latest = itemLatest;
      
    }
    
    return latest;
    
  }

  
  /**
   * Recursive helper method that finds the latest timestamp for a 
   * file/directory.
   * @param file the file to check
   * @param latest set to 0 for start of recursion. 
   * @return the latest timestamp of the file/directory.
   */
  private long findLatest( File file, long latest ) {
    
    if ( file.isDirectory() ) {
      File[] files = file.listFiles();
      for( int i = 0; i < files.length; i++ ) {
        latest = findLatest(files[i], latest);
      }
    }
    
    long lastMod = file.lastModified();

    return lastMod > latest ? lastMod : latest;
    
  }
  
  
  /**
   * Removes the .gwt-tmp directory in the output and moves
   * the -aux directories to <code>target/gwt</code>
   * @param modules the list of modules.
   * @throws IOException
   */
  private void cleanUpAfterCompile() throws IOException {
    
    File remove = new File( warSourceDirectory, ".gwt-tmp" );

    FileUtils.deleteDirectory(remove);
    
    for ( String module : modules ) {
      
      File auxDir = new File( warSourceDirectory, module+"-aux" );

      if ( auxDir.exists() ) {
        
        File moveTo = new File( gwtHome, module+"-aux" );
 
        if ( moveTo.exists() ) FileUtils.deleteDirectory(moveTo);
        
        auxDir.renameTo( moveTo );
        
      }
      
    }
      
  }

  
}
